package edu.tces482.plms.zigbee;


import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;

import edu.tces482.plms.ParkingLot;
import edu.tces482.plms.util.ByteQueue;

public class ZBPacketProcessor {
	
	private static final Logger LOGGER = Logger.getLogger(ZBPacketProcessor.class.getName());
	
	private static enum ZBProcessorState {
		IDLE,
		START,
		READ_LENGTH,
		READ_FRAME,
		STOP
	}
	
	private ByteQueue dataQueue = new ByteQueue();
	
	private Thread processingThread;
	
	private ParkingLot pLot = new ParkingLot();
	
	public ZBPacketProcessor() {
		processingThread = new Thread(new ZBProcessorRunnable());
		processingThread.start();
	}
	
	public boolean submit(final byte [] data) {
		boolean result = dataQueue.add(data);
		if (result)
		{
			LOGGER.fine(data.length + " bytes added");
		}
		return result;
	}
	
	private class ZBProcessorRunnable implements Runnable {

		private ParkingLot pLot = new ParkingLot();
		
		private ZBProcessorState state = ZBProcessorState.IDLE;
		
		private byte [] prefixBytes = new byte [3];
		private byte [] packetBytes;
		private int remainingLength = 0;
		private int copied = 0;
		
		@Override
		public void run() {
			while (state != ZBProcessorState.STOP) {
				switch (state) {
				case IDLE:
					getNextByte();
					break;
					
				case START:
					LOGGER.finer("Found packet start delimiter");
					prefixBytes[0] = 0x7E;
					LOGGER.finest("packetBytes[0] = " + String.format("%02X", prefixBytes[0]));
					copied = 0;
					state = ZBProcessorState.READ_LENGTH;
					break;

				case READ_LENGTH:
					if (copied == 0) 
					{
						prefixBytes[1] = getNextByte();
						LOGGER.finest("packetBytes[1] = " + String.format("%02X", prefixBytes[1]));
						remainingLength = ((0x000000FF & prefixBytes[1]) << 8);
						copied++;
					} 
					else 
					{
						prefixBytes[2] = getNextByte();
						LOGGER.finest("packetBytes[2] = " + String.format("%02X", prefixBytes[2]));
						remainingLength = (remainingLength | (0x000000FF & prefixBytes[2])) + 1;
						packetBytes = Arrays.copyOf(prefixBytes, remainingLength + 3);
						copied = 0;
						state = ZBProcessorState.READ_FRAME;
						
						LOGGER.finer("Total expected bytes in packet: " + (remainingLength + 3));
					}
					break;

				case READ_FRAME:
					if (copied < remainingLength) 
					{
						packetBytes[copied + 3] = getNextByte();
						LOGGER.finest("packetBytes[" + (copied + 3) + "] = " + String.format("%02X", packetBytes[copied + 3]));
						copied++;
						LOGGER.finer((remainingLength - copied) + " bytes remaining");
					} 
					else 
					{
						try 
						{
							ZBReceivePacket packet = new ZBReceivePacket(packetBytes);
							
							byte [] rfData = packet.getRfData();
							StringBuilder sb = new StringBuilder("Packet received: ");
							for (int i = 0; i < rfData.length; i++) {
								sb.append(String.format("%02X ", rfData[i]));
							}
							LOGGER.info(packet.get64BitSourceAddress().toString());
							LOGGER.info(sb.toString());
							pLot.updateStatus(packet);
						}
						catch (MalformedZBPacketException e)
						{
							LOGGER.log(Level.WARNING, "Couldn't construct receive packet", e);
						}
//						pLot.updateStatus(new ZBReceivePacket(packet));
						copied = 0;
						state = ZBProcessorState.IDLE;
					}
					break;

				default:
					throw new IllegalStateException("Unexpected state encountered: " + state);
				}
			}
		}
		
		private byte getNextByte() 
		{
			byte result = dataQueue.take();
			if (result == (byte) 0x7E) 
			{
				state = ZBProcessorState.START;
			}
			else if (result == (byte) 0x7D) 
			{
				StringBuilder stringer = new StringBuilder(39);
				
				result = dataQueue.take();
				
				stringer.append("Converting escaped byte: 0x7D");
				stringer.append(String.format("%02X", result));
				
				result ^= 0x00000020;
				
				stringer.append(" to 0x");
				stringer.append(String.format("%02X", result));
				LOGGER.finest(stringer.toString());
			}
			return result;
		}
	}
	
}
